home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 68K / Lib / fmt.py < prev    next >
Text File  |  1996-05-20  |  15KB  |  470 lines

  1. # Text formatting abstractions
  2. # Note -- this module is obsolete, it's too slow anyway
  3.  
  4.  
  5. import string
  6. import Para
  7.  
  8.  
  9. # A formatter back-end object has one method that is called by the formatter:
  10. # addpara(p), where p is a paragraph object.  For example:
  11.  
  12.  
  13. # Formatter back-end to do nothing at all with the paragraphs
  14. class NullBackEnd:
  15.     #
  16.     def __init__(self):
  17.         pass
  18.     #
  19.     def addpara(self, p):
  20.         pass
  21.     #
  22.     def bgn_anchor(self, id):
  23.         pass
  24.     #
  25.     def end_anchor(self, id):
  26.         pass
  27.  
  28.  
  29. # Formatter back-end to collect the paragraphs in a list
  30. class SavingBackEnd(NullBackEnd):
  31.     #
  32.     def __init__(self):
  33.         self.paralist = []
  34.     #
  35.     def addpara(self, p):
  36.         self.paralist.append(p)
  37.     #
  38.     def hitcheck(self, h, v):
  39.         hits = []
  40.         for p in self.paralist:
  41.             if p.top <= v <= p.bottom:
  42.                 for id in p.hitcheck(h, v):
  43.                     if id not in hits:
  44.                         hits.append(id)
  45.         return hits
  46.     #
  47.     def extract(self):
  48.         text = ''
  49.         for p in self.paralist:
  50.             text = text + (p.extract())
  51.         return text
  52.     #
  53.     def extractpart(self, long1, long2):
  54.         if long1 > long2: long1, long2 = long2, long1
  55.         para1, pos1 = long1
  56.         para2, pos2 = long2
  57.         text = ''
  58.         while para1 < para2:
  59.             ptext = self.paralist[para1].extract()
  60.             text = text + ptext[pos1:]
  61.             pos1 = 0
  62.             para1 = para1 + 1
  63.         ptext = self.paralist[para2].extract()
  64.         return text + ptext[pos1:pos2]
  65.     #
  66.     def whereis(self, d, h, v):
  67.         total = 0
  68.         for i in range(len(self.paralist)):
  69.             p = self.paralist[i]
  70.             result = p.whereis(d, h, v)
  71.             if result <> None:
  72.                 return i, result
  73.         return None
  74.     #
  75.     def roundtowords(self, long1, long2):
  76.         i, offset = long1
  77.         text = self.paralist[i].extract()
  78.         while offset > 0 and text[offset-1] <> ' ': offset = offset-1
  79.         long1 = i, offset
  80.         #
  81.         i, offset = long2
  82.         text = self.paralist[i].extract()
  83.         n = len(text)
  84.         while offset < n-1 and text[offset] <> ' ': offset = offset+1
  85.         long2 = i, offset
  86.         #
  87.         return long1, long2
  88.     #
  89.     def roundtoparagraphs(self, long1, long2):
  90.         long1 = long1[0], 0
  91.         long2 = long2[0], len(self.paralist[long2[0]].extract())
  92.         return long1, long2
  93.  
  94.  
  95. # Formatter back-end to send the text directly to the drawing object
  96. class WritingBackEnd(NullBackEnd):
  97.     #
  98.     def __init__(self, d, width):
  99.         self.d = d
  100.         self.width = width
  101.         self.lineno = 0
  102.     #
  103.     def addpara(self, p):
  104.         self.lineno = p.render(self.d, 0, self.lineno, self.width)
  105.  
  106.  
  107. # A formatter receives a stream of formatting instructions and assembles
  108. # these into a stream of paragraphs on to a back-end.  The assembly is
  109. # parametrized by a text measurement object, which must match the output
  110. # operations of the back-end.  The back-end is responsible for splitting
  111. # paragraphs up in lines of a given maximum width.  (This is done because
  112. # in a windowing environment, when the window size changes, there is no
  113. # need to redo the assembly into paragraphs, but the splitting into lines
  114. # must be done taking the new window size into account.)
  115.  
  116.  
  117. # Formatter base class.  Initialize it with a text measurement object,
  118. # which is used for text measurements, and a back-end object,
  119. # which receives the completed paragraphs.  The formatting methods are:
  120. # setfont(font)
  121. # setleftindent(nspaces)
  122. # setjust(type) where type is 'l', 'c', 'r', or 'lr'
  123. # flush()
  124. # vspace(nlines)
  125. # needvspace(nlines)
  126. # addword(word, nspaces)
  127. class BaseFormatter:
  128.     #
  129.     def __init__(self, d, b):
  130.         # Drawing object used for text measurements
  131.         self.d = d
  132.         #
  133.         # BackEnd object receiving completed paragraphs
  134.         self.b = b
  135.         #
  136.         # Parameters of the formatting model
  137.         self.leftindent = 0
  138.         self.just = 'l'
  139.         self.font = None
  140.         self.blanklines = 0
  141.         #
  142.         # Parameters derived from the current font
  143.         self.space = d.textwidth(' ')
  144.         self.line = d.lineheight()
  145.         self.ascent = d.baseline()
  146.         self.descent = self.line - self.ascent
  147.         #
  148.         # Parameter derived from the default font
  149.         self.n_space = self.space
  150.         #
  151.         # Current paragraph being built
  152.         self.para = None
  153.         self.nospace = 1
  154.         #
  155.         # Font to set on the next word
  156.         self.nextfont = None
  157.     #
  158.     def newpara(self):
  159.         return Para.Para()
  160.     #
  161.     def setfont(self, font):
  162.         if font == None: return
  163.         self.font = self.nextfont = font
  164.         d = self.d
  165.         d.setfont(font)
  166.         self.space = d.textwidth(' ')
  167.         self.line = d.lineheight()
  168.         self.ascent = d.baseline()
  169.         self.descent = self.line - self.ascent
  170.     #
  171.     def setleftindent(self, nspaces):
  172.         self.leftindent = int(self.n_space * nspaces)
  173.         if self.para:
  174.             hang = self.leftindent - self.para.indent_left
  175.             if hang > 0 and self.para.getlength() <= hang:
  176.                 self.para.makehangingtag(hang)
  177.                 self.nospace = 1
  178.             else:
  179.                 self.flush()
  180.     #
  181.     def setrightindent(self, nspaces):
  182.         self.rightindent = int(self.n_space * nspaces)
  183.         if self.para:
  184.             self.para.indent_right = self.rightindent
  185.             self.flush()
  186.     #
  187.     def setjust(self, just):
  188.         self.just = just
  189.         if self.para:
  190.             self.para.just = self.just
  191.     #
  192.     def flush(self):
  193.         if self.para:
  194.             self.b.addpara(self.para)
  195.             self.para = None
  196.             if self.font <> None:
  197.                 self.d.setfont(self.font)
  198.         self.nospace = 1
  199.     #
  200.     def vspace(self, nlines):
  201.         self.flush()
  202.         if nlines > 0:
  203.             self.para = self.newpara()
  204.             tuple = None, '', 0, 0, 0, int(nlines*self.line), 0
  205.             self.para.words.append(tuple)
  206.             self.flush()
  207.             self.blanklines = self.blanklines + nlines
  208.     #
  209.     def needvspace(self, nlines):
  210.         self.flush() # Just to be sure
  211.         if nlines > self.blanklines:
  212.             self.vspace(nlines - self.blanklines)
  213.     #
  214.     def addword(self, text, space):
  215.         if self.nospace and not text:
  216.             return
  217.         self.nospace = 0
  218.         self.blanklines = 0
  219.         if not self.para:
  220.             self.para = self.newpara()
  221.             self.para.indent_left = self.leftindent
  222.             self.para.just = self.just
  223.             self.nextfont = self.font
  224.         space = int(space * self.space)
  225.         self.para.words.append(self.nextfont, text, \
  226.             self.d.textwidth(text), space, space, \
  227.             self.ascent, self.descent)
  228.         self.nextfont = None
  229.     #
  230.     def bgn_anchor(self, id):
  231.         if not self.para:
  232.             self.nospace = 0
  233.             self.addword('', 0)
  234.         self.para.bgn_anchor(id)
  235.     #
  236.     def end_anchor(self, id):
  237.         if not self.para:
  238.             self.nospace = 0
  239.             self.addword('', 0)
  240.         self.para.end_anchor(id)
  241.  
  242.  
  243. # Measuring object for measuring text as viewed on a tty
  244. class NullMeasurer:
  245.     #
  246.     def __init__(self):
  247.         pass
  248.     #
  249.     def setfont(self, font):
  250.     
  251.         self.height = 0
  252.         for p in self.paralist:
  253.             p.layout(self.width)
  254.             p.left = 0
  255.             p.top = self.height
  256.             p.right = self.width
  257.             p.bottom = self.height + p.height
  258.             self.height = p.bottom
  259.         self.window.change((0, 0), (self.width, self.height))
  260.         self.window.setdocsize(0, self.height)
  261.     #
  262.     def redraw(self, area):
  263.         d = self.window.begindrawing()
  264.         (left, top), (right, bottom) = area
  265.         d.erase(area)
  266.         d.cliprect(area)
  267.         for p in self.paralist:
  268.             if top < p.bottom and p.top < bottom:
  269.                 v = p.render(d, p.left, p.top, p.right)
  270.         if self.selection:
  271.             self.invert(d, self.selection)
  272.         d.close()
  273.     #
  274.     def setselection(self, new):
  275.         if new:
  276.             long1, long2 = new
  277.             pos1 = long1[:3]
  278.             pos2 = long2[:3]
  279.             new = pos1, pos2
  280.         if new <> self.selection:
  281.             d = self.window.begindrawing()
  282.             if self.selection:
  283.                 self.invert(d, self.selection)
  284.             if new:
  285.                 self.invert(d, new)
  286.             d.close()
  287.             self.selection = new
  288.     #
  289.     def getselection(self):
  290.         return self.selection
  291.     #
  292.     def extractselection(self):
  293.         if self.selection:
  294.             a, b = self.selection
  295.             return self.extractpart(a, b)
  296.         else:
  297.             return None
  298.     #
  299.     def invert(self, d, region):
  300.         long1, long2 = region
  301.         if long1 > long2: long1, long2 = long2, long1
  302.         para1, pos1 = long1
  303.         para2, pos2 = long2
  304.         while para1 < para2:
  305.             self.paralist[para1].invert(d, pos1, None)
  306.             pos1 = None
  307.             para1 = para1 + 1
  308.         self.paralist[para2].invert(d, pos1, pos2)
  309.     #
  310.     def search(self, prog):
  311.         import regex, string
  312.         if type(prog) == type(''):
  313.             prog = regex.compile(string.lower(prog))
  314.         if self.selection:
  315.             iold = self.selection[0][0]
  316.         else:
  317.             iold = -1
  318.         hit = None
  319.         for i in range(len(self.paralist)):
  320.             if i == iold or i < iold and hit:
  321.                 continue
  322.             p = self.paralist[i]
  323.             text = string.lower(p.extract())
  324.             if prog.search(text) >= 0:
  325.                 a, b = prog.regs[0]
  326.                 long1 = i, a
  327.                 long2 = i, b
  328.                 hit = long1, long2
  329.                 if i > iold:
  330.                     break
  331.         if hit:
  332.             self.setselection(hit)
  333.             i = hit[0][0]
  334.             p = self.paralist[i]
  335.             self.window.show((p.left, p.top), (p.right, p.bottom))
  336.             return 1
  337.         else:
  338.             return 0
  339.     #
  340.     def showanchor(self, id):
  341.         for i in range(len(self.paralist)):
  342.             p = self.paralist[i]
  343.             if p.hasanchor(id):
  344.                 long1 = i, 0
  345.                 long2 = i, len(p.extract())
  346.                 hit = long1, long2
  347.                 self.setselection(hit)
  348.                 self.window.show( \
  349.                     (p.left, p.top), (p.right, p.bottom))
  350.                 break
  351.  
  352.  
  353. # GL extensions
  354.  
  355. class GLFontCache:
  356.     #
  357.     def __init__(self):
  358.         self.reset()
  359.         self.setfont('')
  360.     #
  361.     def reset(self):
  362.         self.fontkey = None
  363.         self.fonthandle = None
  364.         self.fontinfo = None
  365.         self.fontcache = {}
  366.     #
  367.     def close(self):
  368.         self.reset()
  369.     #
  370.     def setfont(self, fontkey):
  371.         if fontkey == '':
  372.             fontkey = 'Times-Roman 12'
  373.         elif ' ' not in fontkey:
  374.             fontkey = fontkey + ' 12'
  375.         if fontkey == self.fontkey:
  376.             return
  377.         if self.fontcache.has_key(fontkey):
  378.             handle = self.fontcache[fontkey]
  379.         else:
  380.             import string
  381.             i = string.index(fontkey, ' ')
  382.             name, sizestr = fontkey[:i], fontkey[i:]
  383.             size = eval(sizestr)
  384.             key1 = name + ' 1'
  385.             key = name + ' ' + `size`
  386.             # NB key may differ from fontkey!
  387.             if self.fontcache.has_key(key):
  388.                 handle = self.fontcache[key]
  389.             else:
  390.                 if self.fontcache.has_key(key1):
  391.                     handle = self.fontcache[key1]
  392.                 else:
  393.                     import fm
  394.                     handle = fm.findfont(name)
  395.                     self.fontcache[key1] = handle
  396.                 handle = handle.scalefont(size)
  397.                 self.fontcache[fontkey] = \
  398.                     self.fontcache[key] = handle
  399.         self.fontkey = fontkey
  400.         if self.fonthandle <> handle:
  401.             self.fonthandle = handle
  402.             self.fontinfo = handle.getfontinfo()
  403.             handle.setfont()
  404.  
  405.  
  406. class GLMeasurer(GLFontCache):
  407.     #
  408.     def textwidth(self, text):
  409.         return self.fonthandle.getstrwidth(text)
  410.     #
  411.     def baseline(self):
  412.         return self.fontinfo[6] - self.fontinfo[3]
  413.     #
  414.     def lineheight(self):
  415.         return self.fontinfo[6]
  416.  
  417.  
  418. class GLWriter(GLFontCache):
  419.     #
  420.     # NOTES:
  421.     # (1) Use gl.ortho2 to use X pixel coordinates!
  422.     #
  423.     def text(self, (h, v), text):
  424.         import gl, fm
  425.         gl.cmov2i(h, v + self.fontinfo[6] - self.fontinfo[3])
  426.         fm.prstr(text)
  427.     #
  428.     def setfont(self, fontkey):
  429.         oldhandle = self.fonthandle
  430.         GLFontCache.setfont(fontkey)
  431.         if self.fonthandle <> oldhandle:
  432.             handle.setfont()
  433.  
  434.  
  435. class GLMeasurerWriter(GLMeasurer, GLWriter):
  436.     pass
  437.  
  438.  
  439. class GLBackEnd(SavingBackEnd):
  440.     #
  441.     def __init__(self, wid):
  442.         import gl
  443.         gl.winset(wid)
  444.         self.wid = wid
  445.         self.width = gl.getsize()[1]
  446.         self.height = 0
  447.         self.d = GLMeasurerWriter()
  448.         SavingBackEnd.__init__(self)
  449.     #
  450.     def finish(self):
  451.         pass
  452.     #
  453.     def addpara(self, p):
  454.         self.paralist.append(p)
  455.         self.height = p.render(self.d, 0, self.height, self.width)
  456.     #
  457.     def redraw(self):
  458.         import gl
  459.         gl.winset(self.wid)
  460.         width = gl.getsize()[1]
  461.         if width <> self.width:
  462.             setdocsize = 1
  463.             self.width = width
  464.             for p in self.paralist:
  465.                 p.top = p.bottom = None
  466.         d = self.d
  467.         v = 0
  468.         for p in self.paralist:
  469.             v = p.render(d, 0, v, width)
  470.